package com.vcc.service.impl;

import cn.hutool.core.date.DateTime;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.vcc.core.exception.NotFoundException;
import com.vcc.core.exception.ProgramException;
import com.vcc.core.util.RegexUtil;
import com.vcc.data.entity.Currency;
import com.vcc.data.entity.UserCardWallet;
import com.vcc.data.entity.UserRate;
import com.vcc.data.entity.Wallet;
import com.vcc.data.mapper.WalletMapper;
import com.vcc.service.sa.BossUserUtil;
import com.vcc.service.service.*;
import jakarta.annotation.Resource;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.vcc.data.entity.table.BossUserTableDef.BOSS_USER;
import static com.vcc.data.entity.table.UserTableDef.USER;
import static com.vcc.data.entity.table.WalletTableDef.WALLET;

@Service
public class WalletServiceImpl extends ServiceImpl<WalletMapper, Wallet> implements WalletService {

    private static final Logger logger = LoggerFactory.getLogger(WalletServiceImpl.class);

    @Resource
    private UserRateService userRateService;
    @Resource
    private RateService rateService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private WalletDetailService walletDetailService;
    @Resource
    private UserCardWalletService userCardWalletService;

    public List<Wallet> setWalletRate(Long userId, List<Wallet> wallets) {
        UserRate userRate;
        try {
            userRate = userRateService.getByUserId(userId);
        } catch (Exception e) {
            return wallets.stream().peek(it -> {
                it.setWithdrawRate(null);
                it.setExchangeRate(null);
            }).toList();
        }
        BigDecimal activityRate;
        if (RegexUtil.checkObjectIsNotNull(userRate.getActivityRate()) && RegexUtil.checkObjectIsNotNull(userRate.getActivityStartDate()) && RegexUtil.checkObjectIsNotNull(userRate.getActivityEndDate())) {
            //如果有活动费率，判断当前时间是否在活动时间内
            //如果在活动时间内，使用活动费率，否则使用普通费率
            if (DateTime.now().isIn(userRate.getActivityStartDate(), userRate.getActivityEndDate())) {
                activityRate = userRate.getActivityRate();
            } else {
                activityRate = userRate.getRate();
            }
        } else {
            activityRate = userRate.getRate();
        }
        for (Wallet wallet : wallets) {
            wallet.setExchangeRate(activityRate);
            wallet.setWithdrawRate(userRate.getClearRate());
        }
        return wallets;
    }


    public Wallet setWalletRate(Long userId, Wallet wallet) {
        UserRate userRate = userRateService.getByUserId(userId);
        BigDecimal activityRate;
        if (RegexUtil.checkObjectIsNotNull(userRate.getActivityRate()) && RegexUtil.checkObjectIsNotNull(userRate.getActivityStartDate()) && RegexUtil.checkObjectIsNotNull(userRate.getActivityEndDate())) {
            //如果有活动费率，判断当前时间是否在活动时间内
            //如果在活动时间内，使用活动费率，否则使用普通费率
            if (DateTime.now().isIn(userRate.getActivityStartDate(), userRate.getActivityEndDate())) {
                activityRate = userRate.getActivityRate();
            } else {
                activityRate = userRate.getRate();
            }
        } else {
            activityRate = userRate.getRate();
        }

        wallet.setExchangeRate(activityRate);
        wallet.setWithdrawRate(userRate.getClearRate());

        return wallet;
    }

    @Override
    public List<Wallet> listByUserId(Long userId) {
        List<Wallet> wallets = queryChain().eq(Wallet::getUserId, userId).list();
        return setWalletRate(userId, wallets);
    }

    @Override
    public List<Wallet> listByUserIds(List<Long> userIds) {
        List<Wallet> wallets = queryChain().in(Wallet::getUserId, userIds).list();
        for (Wallet wallet : wallets) {
            setWalletRate(wallet.getUserId(), wallet);
        }
        return wallets;
    }

    @Override
    public Boolean init(List<Currency> currencies, Long userId) {
        currencies.forEach(it -> {
            Wallet wallet = new Wallet(
                    userId,
                    it.getId(),
                    it.getCode(),
                    BigDecimal.valueOf(0),
                    BigDecimal.valueOf(0),
                    BigDecimal.valueOf(0),
                    BigDecimal.valueOf(0),
                    0L
            );
            save(wallet);
        });
        userRateService.userInit(userId);
        return true;
    }

    @Override
    public Boolean init(Long currencyId, String currencyCode, List<Long> userIds) {
        List<Wallet> wallets = new ArrayList<>();
        for (int i = 0; i < userIds.size(); i++) {
            Wallet wallet = new Wallet(
                    userIds.get(i),
                    currencyId,
                    currencyCode,
                    BigDecimal.valueOf(0),
                    BigDecimal.valueOf(0),
                    BigDecimal.valueOf(0),
                    BigDecimal.valueOf(0),
                    0L
            );
            wallets.add(wallet);
            if (wallets.size() == 1000 || i == (userIds.size() - 1)) {
                saveBatch(wallets);
                wallets.clear();
            }
        }
        return true;
    }

    @Override
    public Wallet getById(Serializable walletId) {
        Wallet wallet = queryChain().eq(Wallet::getId, walletId).oneOpt().orElseThrow(() -> new NotFoundException("没有当前钱包"));
        return setWalletRate(wallet.getUserId(), wallet);
    }

    @Override
    public Wallet getNotRateWalletById(Long walletId) {
        return queryChain().eq(Wallet::getId, walletId).oneOpt().orElseThrow(() -> new NotFoundException("没有当前钱包"));
    }

    @Override
    public Wallet getByUserIdAndCurrencyCode(Long userId, String currencyCode) {
        Wallet wallet = queryChain()
                .eq(Wallet::getUserId, userId)
                .eq(Wallet::getCurrencyCode, currencyCode)
                .oneOpt()
                .orElseThrow(() -> new NotFoundException("没有当前钱包"));
        return setWalletRate(wallet.getUserId(), wallet);
    }

    @Override
    public Wallet getByUserIdAndCurrencyCodeNoRate(Long userId, String currencyCode) {
        return queryChain()
                .eq(Wallet::getUserId, userId)
                .eq(Wallet::getCurrencyCode, currencyCode)
                .oneOpt()
                .orElseThrow(() -> new NotFoundException("没有当前钱包"));
    }

    @Override
    public Boolean openCard(
            Long userId,
            Long operatorUserId,
            String currencyCode,
            String orderId,
            Long cardTypeId,
            String channelName,
            BigDecimal amount,
            String remark,
            String dateSource) {
        Wallet wallet = getByUserIdAndCurrencyCode(userId, currencyCode);
        BigDecimal openBalance = subtractUsableQuotaById(
                wallet.getId(),
                amount,
                2,
                false
        );
        //记录明细
        walletDetailService.openCard(
                cardTypeId,
                channelName,
                orderId,
                remark,
                amount,
                openBalance,
                wallet.getCurrencyId(),
                userId
        );
        return true;
    }

    @Override
    public Boolean cardRecharge(Long userId, Long operatorUserId, String currencyCode, String orderId, Long cardTypeId, String cardNumber, Long userCardId, String channelName, BigDecimal amount, String remark, String dateSource) {
        Wallet wallet = getByUserIdAndCurrencyCode(userId, currencyCode);
        UserCardWallet userCardWallet = userCardWalletService.findByUserIdAndCardId(userId, cardTypeId);
        //如果开卡失败，退回余额
        BigDecimal rechargeAmount = amount;
        if (userCardWallet.getExchangeRate().compareTo(BigDecimal.ZERO) > 0) {
            //如果有汇率，扣除手续费
            BigDecimal rechargeServiceFee = userCardWallet.getExchangeRate()
                    .divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP)
                    .multiply(amount)
                    .setScale(2, RoundingMode.HALF_UP);
            if (rechargeServiceFee.compareTo(BigDecimal.valueOf(0.01)) < 0) {
                rechargeServiceFee = BigDecimal.valueOf(0.01);
            }
            rechargeAmount = rechargeAmount.add(rechargeServiceFee);
        }
        //扣完充值费+充值手续费得余额
        BigDecimal rechargeBalance = subtractUsableQuotaById(
                wallet.getId(),
                rechargeAmount,
                2,
                false
        );
        BigDecimal tempBalance = rechargeBalance.add(rechargeAmount).setScale(2, RoundingMode.HALF_UP);
        //记录明细
        walletDetailService.recharge(
                userCardId,
                cardTypeId,
                channelName,
                cardNumber,
                orderId,
                remark,
                amount,
                tempBalance.subtract(amount),
                wallet.getCurrencyId(),
                userId
        );
        if (userCardWallet.getExchangeRate().compareTo(BigDecimal.ZERO) > 0) {
            //如果有汇率，扣除手续费
            BigDecimal rechargeServiceFee = userCardWallet.getExchangeRate()
                    .divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP)
                    .multiply(amount)
                    .setScale(2, RoundingMode.HALF_UP);
            walletDetailService.rechargeServiceCharge(
                    userCardId,
                    cardTypeId,
                    channelName,
                    cardNumber,
                    orderId,
                    remark,
                    rechargeServiceFee,
                    rechargeBalance,
                    wallet.getCurrencyId(),
                    userId
            );
        }
        return true;
    }

    @Override
    public Boolean systemCardRecharge(Long userId, Long operatorUserId, String currencyCode, String orderId, Long cardTypeId, String cardNumber, Long userCardId, String channelName, BigDecimal amount, String remark, String dateSource) {
        Wallet wallet = getByUserIdAndCurrencyCode(userId, currencyCode);
        UserCardWallet userCardWallet = userCardWalletService.findByUserIdAndCardId(userId, cardTypeId);
        //如果开卡失败，退回余额
        //扣完充值费+充值手续费得余额
        BigDecimal rechargeBalance = subtractUsableQuotaById(
                wallet.getId(),
                amount,
                2,
                true
        );
        //记录明细
        walletDetailService.recharge(
                userCardId,
                cardTypeId,
                channelName,
                cardNumber,
                orderId,
                remark,
                amount,
                rechargeBalance,
                wallet.getCurrencyId(),
                userId
        );
        return true;
    }

    @Override
    public Boolean cardWithdraw(Long userId, Long operatorUserId, String currencyCode, String orderId, Long cardTypeId, String cardNumber, Long userCardId, String channelName, BigDecimal amount, String remark, String dateSource) {
        Wallet wallet = getByUserIdAndCurrencyCode(userId, currencyCode);
        BigDecimal balance = addUsableQuotaByUserIdAndCurrencyId(
                userId,
                wallet.getCurrencyId(),
                amount,
                1,
                true
        );
        walletDetailService.out(
                userCardId,
                cardTypeId,
                channelName,
                cardNumber,
                orderId,
                remark,
                amount,
                balance,
                wallet.getCurrencyId(),
                userId
        );
        return true;
    }

    @Override
    public Wallet getByUserIdAndCurrencyId(Long userId, Long currencyId) {
        Wallet wallet = queryChain()
                .eq(Wallet::getUserId, userId)
                .eq(Wallet::getCurrencyId, currencyId)
                .oneOpt()
                .orElseThrow(() -> new NotFoundException("没有当前钱包"));
        return setWalletRate(wallet.getUserId(), wallet);
    }

    @Override
    public BigDecimal getUsableQuotaByUserIdAndCurrencyCode(Long userId, String currencyCode) {
        return mapper.findUsableQuotaByUserIdAndCurrencyCode(userId, currencyCode);
    }

    @Override
    public BigDecimal getUsableQuotaByUserIdAndCurrencyId(Long userId, Long currencyId) {
        return mapper.findUsableQuotaByUserIdAndCurrencyId(userId, currencyId);
    }

    @Override
    public Boolean updateRateById(Long id, BigDecimal rate) {
        return updateChain().eq(Wallet::getId, id).set(Wallet::getExchangeRate, rate).update();
    }

    @Override
    public BigDecimal updateUsableQuotaById(Long id, BigDecimal amount, Integer flag, Integer useFlag, Boolean otExStatus) {
        return updateAmt(id, null, amount, null, flag, useFlag, otExStatus);
    }

    @Override
    public BigDecimal addUsableQuotaById(Long id, BigDecimal amount, Integer useFlag, Boolean otExStatus) {
        return updateAmt(id, null, amount, null, 1, useFlag, otExStatus);
    }

    @Override
    public BigDecimal subtractUsableQuotaById(Long id, BigDecimal amount, Integer useFlag, Boolean otExStatus) {
        return updateAmt(id, null, amount, null, 2, useFlag, otExStatus);
    }

    @Override
    public BigDecimal updateUsableQuotaByUserIdAndCurrencyId(Long userId, Long currencyId, BigDecimal amount, Integer flag, Integer useFlag, Boolean otExStatus) {
        return updateAmt(null, userId, amount, currencyId, flag, useFlag, otExStatus);

    }

    @Override
    public BigDecimal addUsableQuotaByUserIdAndCurrencyId(Long userId, Long currencyId, BigDecimal amount, Integer useFlag, Boolean otExStatus) {
        return updateAmt(null, userId, amount, currencyId, 1, useFlag, otExStatus);

    }

    @Override
    public BigDecimal subtractUsableQuotaByUserIdAndCurrencyId(Long userId, Long currencyId, BigDecimal amount, Integer useFlag, Boolean otExStatus) {
        return updateAmt(null, userId, amount, currencyId, 2, useFlag, otExStatus);
    }

    @Override
    public Map<String, BigDecimal> summaryBalance() {
        QueryChain<Wallet> queryChain = queryChain().select("SUM(CASE WHEN usable_quota > 0 THEN usable_quota ELSE 0 END) as positiveNumber", "SUM(CASE WHEN usable_quota < 0 THEN usable_quota ELSE 0 END) as negativeNumber");
        if (!BossUserUtil.hasAllData()) {
            Long bossUserId = BossUserUtil.getId();
            //查询余额正数之和 和 负数之和
            queryChain.leftJoin(USER).on(WALLET.USER_ID.eq(USER.ID));
            queryChain.leftJoin(BOSS_USER.as("dealer")).on(BOSS_USER.as("dealer").ID.eq(USER.BE_LONG_TO_DEALER));
            queryChain.and(
                    BOSS_USER.as("dealer").ID.eq(bossUserId)
                            .or(BOSS_USER.as("dealer").PARENT_ID.eq(bossUserId)));
        }
        return (Map<String, BigDecimal>) queryChain.oneAs(Map.class);
    }

    private BigDecimal updateAmt(
            Long id,
            Long userId,
            BigDecimal amount,
            Long currencyId,
            Integer flag,
            Integer useFlag,
            Boolean noExStatus
    ) {
        String key = "updateAmount:" + userId;
        RLock lock = redissonClient.getLock(key);
        try {
            lock.lock();
            Wallet wallet;
            if (id == null) {
                wallet = queryChain().eq(Wallet::getUserId, userId).eq(Wallet::getCurrencyId, currencyId).oneOpt().orElseThrow(() -> new NotFoundException("当前账户没有钱包"));
            } else {
                wallet = queryChain().eq(Wallet::getId, id).oneOpt().orElseThrow(() -> new NotFoundException("当前账户没有钱包"));
            }
            logger.info("账户{}调用修改账户可用额度方法,需要调整额度{},维护方式{},来源{}，当前版本为{}",
                    wallet.getUserId(),
                    amount.abs(),
                    flag,
                    useFlag,
                    wallet.getVersion()
            );
            logger.info("调整额度前为{}", wallet.getUsableQuota());
            switch (flag) {
                case 1: {
                    wallet.setUsableQuota(wallet.getUsableQuota().add(amount.abs()).setScale(2, RoundingMode.HALF_UP));
                    break;
                }
                case 2: {
                    if (!noExStatus && wallet.getUsableQuota().compareTo(amount.abs()) < 0) {
                        throw new ProgramException("账户金额不足，不支持消费");
                    }
                    wallet.setUsableQuota(wallet.getUsableQuota().subtract(amount.abs()).setScale(2, RoundingMode.HALF_UP));
                }
            }
            logger.info("调整后额度为{}", wallet.getUsableQuota());
            switch (useFlag) {
                //充值  增加总额度 增加充值总额度
                case 1: {
                    wallet.setRechargeQuota(wallet.getRechargeQuota().add(amount.abs()));
                }
                //消费
                case 2: {
                    wallet.setConsumptionQuota(wallet.getConsumptionQuota().add(amount.abs()));
                }
                //充值失败
                case 4: {
                    wallet.setConsumptionQuota(wallet.getConsumptionQuota().subtract(amount.abs()));
                }
            }
            boolean result = updateChain().set(Wallet::getConsumptionQuota, wallet.getConsumptionQuota())
                    .set(Wallet::getRechargeQuota, wallet.getRechargeQuota())
                    .set(Wallet::getUsableQuota, wallet.getUsableQuota())
                    .set(Wallet::getVersion, wallet.getVersion() + 1)
                    .eq(Wallet::getId, wallet.getId())
                    .eq(Wallet::getVersion, wallet.getVersion()).update();
            if (!result) {
                logger.info("调整失败，需重新修改");
                return updateAmt(id, userId, amount.abs(), currencyId, flag, useFlag, noExStatus);
            } else {
                return wallet.getUsableQuota();
            }
        } catch (ProgramException e) {
            logger.error("操作失败", e);
            throw e;
        } catch (Exception e) {
            logger.error("操作失败", e);
            throw new ProgramException("操作失败请稍后重试");
        } finally {
            lock.unlock();
        }
    }


}
